/*
 * Spinneret - Semi-Structured Database Engine
 * Copyright (c) 2009 Tom Bradford <spinneretdb.org>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include "buffers/fspager.h"
#include "utils/thread.h"

/* Private Types and Variables ***********************************************/

/** Implementation type for File System-based Pager instances */
struct sdb_fspager_t {
    sdb_pager_t _super;
    char* path;
    FILE* fp;
    sdb_mutex_t* mutex;
};

/* Private Functions *********************************************************/

static sdb_status_t private_open(sdb_pager_t* pager)
{
    sdb_fspager_t* fspager = (sdb_fspager_t*)pager;

    fspager->fp = fopen(fspager->path, "w+");

    if ( fspager->fp )
        return SDB_SUCCESS;
    else
        return SDB_FAILURE;
}

/*---------------------------------------------------------------------------*/

static sdb_status_t private_create(sdb_pager_t* pager)
{
    sdb_fspager_t* fspager = (sdb_fspager_t*)pager;

    fspager->fp = fopen(fspager->path, "w");

    if ( fspager->fp ) {
        return SDB_SUCCESS;
    }
    else
        return SDB_FAILURE;
}

/*---------------------------------------------------------------------------*/

static sdb_status_t private_close(sdb_pager_t* pager)
{
    sdb_fspager_t* fspager = (sdb_fspager_t*)pager;

    if ( fspager->fp ) {
        fclose(fspager->fp);
        fspager->fp = NULL;

        return SDB_SUCCESS;
    }
    else
        return SDB_FAILURE;
}

/*---------------------------------------------------------------------------*/

static sdb_status_t private_unlink(sdb_pager_t* pager)
{
    sdb_fspager_t* fspager = (sdb_fspager_t*)pager;

    int res = unlink(fspager->path);
    if ( res == 0 )
        return SDB_SUCCESS;
    else
        return SDB_FAILURE;
}

/*---------------------------------------------------------------------------*/

static sdb_status_t private_info(sdb_pager_t* pager, sdb_pager_info_t* info)
{
    sdb_fspager_t* fspager = (sdb_fspager_t*)pager;

    struct stat st;
    int res = stat(fspager->path, &st);
    if ( res == 0 ) {
        info->exists = true;
        info->blockSize = st.st_blksize;
        info->fileSize = st.st_size;
        return SDB_SUCCESS;
    }
    else {
        info->exists = false;
        info->blockSize = -1;
        info->fileSize = -1;
        return SDB_FAILURE;
    }
}

/*---------------------------------------------------------------------------*/

static sdb_status_t private_read(sdb_pager_t* pager, sdb_off_t pos,
                                 char* buffer, sdb_size_t len)
{
    sdb_fspager_t* fspager = (sdb_fspager_t*)pager;

    sdb_mutex_lock(fspager->mutex);
    fseeko(fspager->fp, pos, SEEK_SET);
    sdb_size_t res = fread(buffer, 1, len, fspager->fp);
    sdb_mutex_unlock(fspager->mutex);

    if ( res == len )
        return SDB_SUCCESS;
    else
        return SDB_FAILURE;
}

/*---------------------------------------------------------------------------*/

static sdb_status_t private_write(sdb_pager_t* pager, sdb_off_t pos,
                                  char* buffer, sdb_size_t len)
{
    sdb_fspager_t* fspager = (sdb_fspager_t*)pager;

    sdb_mutex_lock(fspager->mutex);
    fseeko(fspager->fp, pos, SEEK_SET);
    sdb_size_t res = fwrite(buffer, 1, len, fspager->fp);
    sdb_mutex_unlock(fspager->mutex);

    if ( res == len )
        return SDB_SUCCESS;
    else
        return SDB_FAILURE;
}

/*---------------------------------------------------------------------------*/

static void private_finalizer(sdb_fspager_t* fspager)
{
    if ( fspager->fp )
        fclose(fspager->fp);

    if ( fspager->mutex )
        sdb_ptr_free(fspager->mutex);
}

/* Public Functions **********************************************************/

sdb_fspager_t* sdb_fspager_new(sdb_mem_t* mem)
{
    return sdb_mem_malloc(mem, sizeof(sdb_fspager_t));
}

/*---------------------------------------------------------------------------*/

sdb_fspager_t* sdb_fspager_init(sdb_fspager_t* fspager, char* path)
{
    static sdb_pager_functions_t functions = {
        .openFunction = private_open,
        .createFunction = private_create,
        .closeFunction = private_close,
        .unlinkFunction = private_unlink,
        .infoFunction = private_info,
        .readFunction = private_read,
        .writeFunction = private_write
    };

    sdb_pager_init((sdb_pager_t*)fspager, &functions);

    sdb_mem_t* mem = sdb_ptr_getMem(fspager);
    fspager->mutex = sdb_mutex_new(mem);

    fspager->path = path;
    fspager->fp = NULL;

    sdb_ptr_setDisposalFunction(fspager, private_finalizer);

    return fspager;
}
